﻿// ------------------------------------------------------------------------------
// <copyright file="Pie.cs" company="skumaronline.com">
//    Copyright (C) 2012 Suresh Kumar T.  All rights reserved.
// </copyright>
// ------------------------------------------------------------------------------

namespace StoreAppLib.Graph
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Input;
    using StoreAppLib.UI;
    using Windows.UI;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media;

    /// <summary>
    /// Pie control
    /// </summary>
    public sealed class Pie : UserControl, IChartType
    {
        /// <summary>
        /// Using a DependencyProperty as the backing store for chart series source.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(Pie), new PropertyMetadata(null, Pie.OnDataSourcePropertyChanged));
        
        /// <summary>
        /// Using a DependencyProperty as the backing store for value column name in the ItemsSource to be used to generate chart.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty SeriesColumnNameProperty =
            DependencyProperty.Register("SeriesColumnName", typeof(string), typeof(Pie), new PropertyMetadata(null, Pie.OnSeriesColumnNamePropertyChanged));

        /// <summary>
        /// Using a DependencyProperty as the backing store for series color column.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty SeriesColorColumnNameProperty =
            DependencyProperty.Register("SeriesColorColumnName", typeof(string), typeof(Pie), new PropertyMetadata(null, Pie.OnSeriesColorColumnNameChanged));

        /// <summary>
        /// Using a DependencyProperty as the backing store for stroke brush.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty StrokeProperty =
            DependencyProperty.Register("Stroke", typeof(Brush), typeof(Pie), new PropertyMetadata(new SolidColorBrush(Colors.White), Pie.OnPresentationPropertyChanged));

        /// <summary>
        /// Using a DependencyProperty as the backing store for stroke thickness.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty StrokeThicknessProperty =
            DependencyProperty.Register("StrokeThickness", typeof(double), typeof(Pie), new PropertyMetadata(2.0, Pie.OnPresentationPropertyChanged));

        /// <summary>
        /// Using a DependencyProperty as the backing store for tap effect value.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty EnableTapEffectProperty =
            DependencyProperty.Register("EnableTapEffect", typeof(bool), typeof(Pie), new PropertyMetadata(true, Pie.OnTemplatePropertyChanged));
        
        /// <summary>
        /// Using a DependencyProperty as the backing store for chart item command.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ChartItemCommandProperty =
            DependencyProperty.Register("ChartItemCommand", typeof(ICommand), typeof(Pie), new PropertyMetadata(null));

        /// <summary>
        /// Variable to hold the Pie control.
        /// </summary>
        private PieItemsControl pieControl;

        /// <summary>
        /// Variable to hold the command to capture chart series click.
        /// </summary>
        private ICommand chartClickCommand; 

        /// <summary>
        /// Initializes a new instance of the <see cref="Pie"/> class"
        /// </summary>
        public Pie()
        {
            this.chartClickCommand = new RelayCommand(this.OnChartItemClick);
            this.pieControl = new PieItemsControl();
            this.Content = this.pieControl;
        }

        /// <summary>
        /// Chart item click event handler
        /// </summary>
        public event ChartItemClickEventHandler ChartItemClick;

        /// <summary>
        /// Gets or sets the chart series source.
        /// </summary>
        /// <value>
        /// The chart series source.
        /// </value>
        public object ItemsSource
        {
            get { return (object)this.GetValue(ItemsSourceProperty); }
            set { this.SetValue(ItemsSourceProperty, value); }
        }

        /// <summary>
        /// Gets or sets the value column name in the ItemsSource to be used to generate chart.
        /// </summary>
        /// <value>
        /// The value column name in the ItemsSource to be used to generate chart.
        /// </value>
        public string SeriesColumnName
        {
            get { return (string)this.GetValue(SeriesColumnNameProperty); }
            set { this.SetValue(SeriesColumnNameProperty, value); }
        }

        /// <summary>
        /// Gets or sets the series color column.
        /// </summary>
        /// <value>
        /// The series color column.
        /// </value>
        public string SeriesColorColumnName
        {
            get { return (string)this.GetValue(SeriesColorColumnNameProperty); }
            set { this.SetValue(SeriesColorColumnNameProperty, value); }
        }

        /// <summary>
        /// Gets or sets the stroke brush.
        /// </summary>
        /// <value>
        /// The stroke brush.
        /// </value>
        public Brush Stroke
        {
            get { return (Brush)this.GetValue(StrokeProperty); }
            set { this.SetValue(StrokeProperty, value); }
        }

        /// <summary>
        /// Gets or sets the stroke thickness.
        /// </summary>
        /// <value>
        /// The stroke thickness.
        /// </value>
        public double StrokeThickness
        {
            get { return (double)this.GetValue(StrokeThicknessProperty); }
            set { this.SetValue(StrokeThicknessProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the tap effect value.
        /// </summary>
        /// <value>
        /// The tap effect value.
        /// </value>
        public bool EnableTapEffect
        {
            get { return (bool)this.GetValue(EnableTapEffectProperty); }
            set { this.SetValue(EnableTapEffectProperty, value); }
        }

        /// <summary>
        /// Gets or sets the chart item command.
        /// </summary>
        /// <value>
        /// The chart item command.
        /// </value>
        public ICommand ChartItemCommand
        {
            get { return (ICommand)this.GetValue(ChartItemCommandProperty); }
            set { this.SetValue(ChartItemCommandProperty, value); }
        }

        /// <summary>
        /// Fires when any property that affect template structure
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private static void OnTemplatePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue != args.NewValue)
            {
                (source as Pie).pieControl.EnableTapEffect = (bool)args.NewValue;
            }
        }

        /// <summary>
        /// Fires when color column property name changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private static void OnSeriesColorColumnNameChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue != args.NewValue)
            {
                (source as Pie).BindItemsSource();
            }
        }        

        /// <summary>
        /// Fires when data source property changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private static void OnDataSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue != args.NewValue)
            {
                INotifyCollectionChanged oldValue = args.OldValue as INotifyCollectionChanged;

                if (oldValue != null)
                {
                    oldValue.CollectionChanged -= (source as Pie).OnItemSourceCollectionChanged;
                }

                INotifyCollectionChanged newValue = args.NewValue as INotifyCollectionChanged;

                if (newValue != null)
                {
                    newValue.CollectionChanged += (source as Pie).OnItemSourceCollectionChanged;
                }

                IEnumerable<INotifyPropertyChanged> itemSource = args.NewValue as IEnumerable<INotifyPropertyChanged>;

                if (itemSource != null)
                {
                    foreach (object item in itemSource)
                    {
                        (source as Pie).AddRemoveNotifyPropertyChanged(item, true);
                    }
                }

                (source as Pie).BindItemsSource();
            }
        }

        /// <summary>
        /// Fires series column name changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private static void OnSeriesColumnNamePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue != args.NewValue)
            {
                (source as Pie).BindItemsSource();
            }
        }

        /// <summary>
        /// Fires when presentation property changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private static void OnPresentationPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue != args.NewValue)
            {
                (source as Pie).UpdatePresentationProperty();
            }
        }

        /// <summary>
        /// Fires when source collection changes
        /// </summary>
        /// <param name="source">The source</param>
        /// <param name="args">The argument</param>
        private void OnItemSourceCollectionChanged(object source, NotifyCollectionChangedEventArgs args)
        {
            System.Collections.IList newItems = null;
            System.Collections.IList oldItems = null;

            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    newItems = args.NewItems;
                    break;
                case NotifyCollectionChangedAction.Remove:
                    oldItems = args.OldItems;
                    break;
                case NotifyCollectionChangedAction.Replace:
                    oldItems = args.OldItems;
                    newItems = args.NewItems;
                    break;
            }

            if (oldItems != null)
            {
                foreach (object item in oldItems)
                {
                    this.AddRemoveNotifyPropertyChanged(item, false);
                }
            }

            if (newItems != null)
            {
                foreach (object item in newItems)
                {
                    this.AddRemoveNotifyPropertyChanged(item, true);
                }
            }

            this.BindItemsSource();
        }

        /// <summary>
        /// When items added or removed from collection, add or remove property change handler
        /// </summary>
        /// <param name="item">The affected item</param>
        /// <param name="add">Value indicating whether value is added or removed</param>
        private void AddRemoveNotifyPropertyChanged(object item, bool add)
        {
            INotifyPropertyChanged propertyItem = item as INotifyPropertyChanged;

            if (propertyItem != null)
            {
                if (add)
                {
                    propertyItem.PropertyChanged += this.OnPropertyChanged;
                }
                else
                {
                    propertyItem.PropertyChanged -= this.OnPropertyChanged;
                }
            }
        }

        /// <summary>
        /// Handler for property change
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="args">The argument</param>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == this.SeriesColumnName)
            {
                this.BindItemsSource();
            }
        }

        /// <summary>
        /// Bind the source data to Pie
        /// </summary>
        private void BindItemsSource()
        {
            if (this.ItemsSource == null)
            {
                this.pieControl.ItemsSource = null;
            }
            else
            {
                IEnumerable<object> source = null;

                if (this.ItemsSource.GetType().IsArray)
                {
                    source = (this.ItemsSource as IEnumerable).OfType<object>();
                }
                else
                {
                    source = this.ItemsSource as IEnumerable<object>;
                }

                if (source == null)
                {
                    this.pieControl.ItemsSource = null;
                }
                else
                {
                    ObservableCollection<PieItem> itemsSource = new ObservableCollection<PieItem>();

                    try
                    {
                        if (string.IsNullOrWhiteSpace(this.SeriesColumnName))
                        {
                            itemsSource = new ObservableCollection<PieItem>(source.Select<object, PieItem>(row => new PieItem() { Source = row, Value = Convert.ToDouble(row) }));
                        }
                        else
                        {
                            itemsSource = new ObservableCollection<PieItem>(source.Select<object, PieItem>(row => new PieItem() { Source = row, Value = Convert.ToDouble(row.GetType().GetRuntimeProperty(this.SeriesColumnName).GetValue(row)) }));
                        }
                    }
                    catch
                    {
                    }

                    double total = itemsSource.Sum(v => v.Value);

                    List<double> values = new List<double>();

                    double previousValue = 0.0;

                    ColorGenerator colorGenerator = new ColorGenerator();

                    Brush colorBrush = null;
                    
                    foreach (PieItem row in itemsSource)
                    {
                        if (!string.IsNullOrWhiteSpace(this.SeriesColorColumnName))
                        {
                            object inputColor = row.Source.GetType().GetRuntimeProperty(this.SeriesColorColumnName).GetValue(row.Source);

                            if (inputColor is Color)
                            {
                                colorBrush = new SolidColorBrush((Color)inputColor);
                            }
                            else if (inputColor is Brush)
                            {
                                colorBrush = inputColor as Brush;
                            }
                        }

                        if (colorBrush == null)
                        {
                            colorBrush = new SolidColorBrush(colorGenerator.GetNextColor());
                        }
                                                
                        row.TapCommand = this.chartClickCommand;

                        row.StartAngle = previousValue;
                        row.Percentage = row.Value / total * 360;
                        row.EndAngle = row.StartAngle + row.Percentage;
                        row.Fill = colorBrush;
                        row.Stroke = this.Stroke;
                        row.StrokeThickness = this.StrokeThickness;
                        previousValue = row.EndAngle;

                        colorBrush = null;
                    }

                    this.pieControl.ItemsSource = itemsSource;
                }
            }
        }

        /// <summary>
        /// Calls when presentation property changes
        /// </summary>
        private void UpdatePresentationProperty()
        {
            IEnumerable<PieItem> arcSeries = this.pieControl.ItemsSource as IEnumerable<PieItem>;

            if (arcSeries != null)
            {
                foreach (PieItem item in arcSeries)
                {
                    if (!object.Equals(item.Stroke, this.Stroke))
                    {
                        item.Stroke = this.Stroke;
                    }

                    if (!object.Equals(item.StrokeThickness, this.StrokeThickness))
                    {
                        item.StrokeThickness = this.StrokeThickness;
                    }
                }
            }
        }

        /// <summary>
        /// Handler for chart item click
        /// </summary>
        /// <param name="chartSource">Chart item source</param>
        private void OnChartItemClick(object chartSource)
        {
            Arc arc = chartSource as Arc;

            ChartItemClickEventArgs argument = new ChartItemClickEventArgs((arc.DataContext as PieItem).Source);

            ChartItemClickEventHandler eventHandler = this.ChartItemClick;

            if (eventHandler != null)
            {
                eventHandler(this, argument);
            }

            if (!argument.Handled)
            {
                ICommand command = this.ChartItemCommand;

                if (command != null)
                {
                    command.Execute(argument.Item);
                }
            }
        }
    }
}
